//------------------------------------------------------------------------------
// ValueEditors.cpp
//------------------------------------------------------------------------------
// File provided for Microsoft Shared Source.
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF 
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO 
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 
// PARTICULAR PURPOSE.
//------------------------------------------------------------------------------
//
// Registry value editors
//
//------------------------------------------------------------------------------

#include "pch.h"
#include <math.h>
#pragma warning(push)
#pragma warning(disable:4995)
#include <atlwin.h>
#pragma warning(pop)
#include <atltypes.h>
#include <RegNamespace.h>
#include <SafeInt.hpp>
#include "BinaryEdit.h"

//------------------------------------------------------------------------------
// CValueEditorBase
//------------------------------------------------------------------------------

DECLARE_SZ(EditorSettingsRootKey, "Software\\Microsoft\\Windows Registry Explorer\\Editor Settings")

#define DECLARE_VALUE_EDITOR(DlgId, TitleId) \
    enum { IDD = DlgId }; \
    enum { IDT = TitleId };

#define DECLARE_NO_SETTINGS() \
    HRESULT SaveSettings(__in INamedPropertyStore *pnps)    { return S_OK; }

template <typename T>
class CValueEditorBase : 
    public CComObjectRootEx<CComSingleThreadModel>,
    public CDialogImplBase,
    public IRegValueEditor
{
public:
    static HRESULT s_CreateEditor(__in IShellItem2 *psi, __in IPropertyStore *ppropstore, REFIID riid, __deref_out void **ppv);

    BEGIN_COM_MAP(CValueEditorBase)
        COM_INTERFACE_ENTRY(IRegValueEditor)
    END_COM_MAP()

    DECLARE_PROTECT_FINAL_CONSTRUCT()

    BEGIN_MSG_MAP(CValueEditorBase)
        MESSAGE_HANDLER(WM_INITDIALOG, _OnInitDialog)
        MESSAGE_HANDLER(WM_DESTROY, _OnDestroy)
        COMMAND_HANDLER(IDOK, BN_CLICKED, _OnOK)
        COMMAND_HANDLER(IDCANCEL, BN_CLICKED, _OnCancel)
    END_MSG_MAP()

    // IRegValueEditor
    STDMETHOD(EditModal)(HWND hwndOwner);

protected:
    HRESULT _Initialize(__in IShellItem2 *psi, __in IPropertyStore *ppropstore);
    HRESULT _LoadSettings(REFIID riid, __deref_out void **ppv);
    HRESULT _SaveSettings();

    // Helpers
    HRESULT _GetProperty(REFPROPERTYKEY key, __out PROPVARIANT *ppropvar);

private:
    HRESULT _SetTitle();
    HRESULT _SetIcon();
    HRESULT _SetValueName();

    // Message handlers
    LRESULT _OnInitDialog(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnDestroy(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnOK(WORD wNotifyCode, WORD wID, HWND hwndCtl, BOOL &fHandled);
    LRESULT _OnCancel(WORD wNotifyCode, WORD wID, HWND hwndCtl, BOOL &fHandled);

private:
    CComPtr<IShellItem2> _spsi;
    CComPtr<IPropertyStore> _sppropstore;
};

template <typename T>
HRESULT CValueEditorBase<T>::_Initialize(__in IShellItem2 *psi, __in IPropertyStore *ppropstore)
{
    _spsi = psi;
    _sppropstore = ppropstore;
    return S_OK;
}

template <typename T>
HRESULT CValueEditorBase<T>::s_CreateEditor(__in IShellItem2 *psi, __in IPropertyStore *ppropstore, REFIID riid, __deref_out void **ppv)
{
    CComObjectWithRef<T> *pspe;
    HRESULT hr = CComObjectWithRef<T>::CreateInstance(&pspe);
    if (SUCCEEDED(hr))
    {
        hr = pspe->_Initialize(psi, ppropstore);
        if (SUCCEEDED(hr))
        {
            hr = pspe->QueryInterface(riid, ppv);
        }
        pspe->Release();
    }
    return hr;
}

template <typename T>
HRESULT CValueEditorBase<T>::_GetProperty(REFPROPERTYKEY key, __out PROPVARIANT *ppropvar)
{
    return _sppropstore->GetValue(key, ppropvar);
}

// IRegValueEditor
template <typename T>
STDMETHODIMP CValueEditorBase<T>::EditModal(HWND hwndOwner)
{
    ATLASSUME(m_hWnd == NULL);

    // Allocate the thunk structure here, where we can fail gracefully.
    HRESULT hr = m_thunk.Init(NULL, NULL) ? S_OK : E_OUTOFMEMORY;
    if (SUCCEEDED(hr))
    {
        _AtlWinModule.AddCreateWndData(&m_thunk.cd, (CDialogImplBase *)this);
        hr = (HRESULT)::DialogBoxParam(_AtlBaseModule.GetResourceInstance(), MAKEINTRESOURCE(static_cast<T*>(this)->IDD), hwndOwner, StartDialogProc, NULL);
    }
    return hr;
}

template <typename T>
HRESULT CValueEditorBase<T>::_SetTitle()
{
    CBufString strTitle;
    HRESULT hr = strTitle.Load(static_cast<T*>(this)->IDT);
    if (SUCCEEDED(hr))
    {
        SetWindowText(strTitle);
    }
    return hr;
}

template <typename T>
HRESULT CValueEditorBase<T>::_SetIcon()
{
    CComPtr<IExtractIcon> spei;
    HRESULT hr = _spsi->BindToHandler(NULL, BHID_SFUIObject, IID_PPV_ARGS(&spei));
    if (SUCCEEDED(hr))
    {
        CPathString strIcon;
        int iIndex;
        UINT uiFlags;
        hr = spei->GetIconLocation(GIL_FORSHELL, strIcon, strIcon.Size(), &iIndex, &uiFlags);
        if (SUCCEEDED(hr))
        {
            HICON hicn;
            hr = spei->Extract(strIcon, iIndex, NULL, &hicn, MAKELONG(GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CXSMICON)));
            if (SUCCEEDED(hr))
            {
                SetIcon(hicn, FALSE);
            }
        }
    }
    return hr;
}

template <typename T>
HRESULT CValueEditorBase<T>::_SetValueName()
{
    CComPropVariant spropvar;
    HRESULT hr = _sppropstore->GetValue(PKEY_ItemNameDisplay, &spropvar);
    if (SUCCEEDED(hr))
    {
        hr = SetDlgItemText(IDC_VALUENAME, spropvar.pwszVal) ? S_OK : AtlHresultFromLastError();
    }
    return hr;
}

template <typename T>
HRESULT CValueEditorBase<T>::_LoadSettings(REFIID riid, __deref_out void **ppv)
{
    CComPtr<INamedPropertyStore> spnps;
    HRESULT hr = PSCreateMemoryPropertyStore(IID_PPV_ARGS(&spnps));
    if (SUCCEEDED(hr))
    {
        CRegKey rk;
        hr = AtlHresultFromWin32(rk.Open(HKEY_CURRENT_USER, c_szEditorSettingsRootKey, KEY_READ));
        if (SUCCEEDED(hr))
        {
            CPathString strValue;
            DWORD cchValue;
            for (DWORD dwIndex = 0; SUCCEEDED(hr); dwIndex++)
            {
                cchValue = strValue.Size();
                DWORD dwType;
                ULONG cb;
                LONG lRet = RegEnumValue(rk, dwIndex, strValue, &cchValue, NULL, &dwType, NULL, &cb);
                if (lRet == ERROR_SUCCESS)
                {
                    CLocalMemPtr<BYTE> spValue;
                    hr = spValue.Allocate(cb);
                    if (SUCCEEDED(hr))
                    {
                        hr = AtlHresultFromWin32(rk.QueryValue(strValue, NULL, spValue, &cb));
                        if (SUCCEEDED(hr))
                        {
                            CComPropVariant spropvar;
                            switch (dwType)
                            {
                            case REG_SZ:
                                hr = InitPropVariantFromString((PWSTR)spValue.m_pData, &spropvar);
                                break;

                            case REG_DWORD:
                                hr = InitPropVariantFromUInt32(*((ULONG *)spValue.m_pData), &spropvar);
                                break;

                            default:
                                ATLASSERT(FALSE);       // Should handle any types needed by derived classes
                                hr = E_UNEXPECTED;
                            }

                            if (SUCCEEDED(hr))
                            {
                                hr = spnps->SetNamedValue(strValue, spropvar);
                            }
                        }
                    }
                }
                else if (lRet != ERROR_NO_MORE_ITEMS)
                {
                    hr = AtlHresultFromWin32(lRet);
                }
                else
                {
                    hr = S_OK;
                    break;
                }
            }
        }
        else if (hr == AtlHresultFromWin32(ERROR_FILE_NOT_FOUND))
        {
            hr = S_OK;
        }

        if (SUCCEEDED(hr))
        {
            hr = spnps->QueryInterface(riid, ppv);
        }
    }
    return hr;
}

template <typename T>
LRESULT CValueEditorBase<T>::_OnInitDialog(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    HRESULT hr = _SetTitle();
    if (SUCCEEDED(hr))
    {
        hr = _SetIcon();
        if (SUCCEEDED(hr))
        {
            hr = _SetValueName();
            if (SUCCEEDED(hr))
            {
                CComPtr<INamedPropertyStore> spnps;
                hr = _LoadSettings(IID_PPV_ARGS(&spnps));
                if (SUCCEEDED(hr))
                {
                    T* pT = static_cast<T*>(this);
                    hr = pT->InitDialog(spnps);
                    if (SUCCEEDED(hr))
                    {
                        CenterWindow();
                    }
                }
            }
        }
    }
    if (FAILED(hr))
    {
        EndDialog(m_hWnd, hr);
    }
    return 0;
}

template <typename T>
HRESULT CValueEditorBase<T>::_SaveSettings()
{
    CRegKey rk;
    HRESULT hr = AtlHresultFromWin32(rk.Create(HKEY_CURRENT_USER, c_szEditorSettingsRootKey));
    if (SUCCEEDED(hr))
    {
        CComPtr<INamedPropertyStore> spnps;
        hr = PSCreateMemoryPropertyStore(IID_PPV_ARGS(&spnps));
        if (SUCCEEDED(hr))
        {
            T* pT = static_cast<T*>(this);
            hr = pT->SaveSettings(spnps);
            if (SUCCEEDED(hr))
            {
                DWORD cProps;
                hr = spnps->GetNameCount(&cProps);
                for (DWORD i = 0; i < cProps && SUCCEEDED(hr); i++)
                {
                    CComBSTR sbstrName;
                    hr = spnps->GetNameAt(i, &sbstrName);
                    if (SUCCEEDED(hr))
                    {
                        CComPropVariant spropvar;
                        hr = spnps->GetNamedValue(sbstrName, &spropvar);
                        if (SUCCEEDED(hr))
                        {
                            switch (spropvar.vt)
                            {
                            case VT_LPWSTR:
                                hr = AtlHresultFromWin32(rk.SetStringValue(sbstrName, spropvar.pwszVal));
                                break;

                            case VT_UI4:
                                hr = AtlHresultFromWin32(rk.SetDWORDValue(sbstrName, spropvar.ulVal));
                                break;

                            default:
                                ATLASSERT(FALSE);   // Should handle any types needed by derived classes
                                hr = E_UNEXPECTED;
                            }
                        }
                    }
                }
            }
        }
    }
    return hr;
}

template <typename T>
LRESULT CValueEditorBase<T>::_OnDestroy(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    // Ignoring the return value here since there isn't much to be done about it
    _SaveSettings();

    HICON hicn = SetIcon(NULL, FALSE);
    if (hicn)
    {
        DestroyIcon(hicn);
    }
    return 0;
}

template <typename T>
LRESULT CValueEditorBase<T>::_OnOK(WORD wNotifyCode, WORD wID, HWND hwndCtl, BOOL &fHandled)
{
    PROPERTYKEY key = PKEY_RegistryData;
    CComPtr<IPropertyStore> sppropstore;
    HRESULT hr = _spsi->GetPropertyStoreForKeys(&key, 1, GPS_READWRITE, IID_PPV_ARGS(&sppropstore));
    if (SUCCEEDED(hr))
    {
        CComPropVariant sppropvar;
        T* pT = static_cast<T*>(this);
        hr = pT->GetCurrentValue(&sppropvar);
        if (SUCCEEDED(hr))
        {
            hr = sppropstore->SetValue(PKEY_RegistryData, sppropvar);
            if (SUCCEEDED(hr))
            {
                hr = sppropstore->Commit();
            }
        }
    }
    EndDialog(m_hWnd, hr);
    return 0;
}

template <typename T>
LRESULT CValueEditorBase<T>::_OnCancel(WORD wNotifyCode, WORD wID, HWND hwndCtl, BOOL &fHandled)
{
    EndDialog(m_hWnd, E_ABORT);
    return 0;
}

//------------------------------------------------------------------------------
// CStringEditor
//------------------------------------------------------------------------------

class CStringEditor :
    public CValueEditorBase<CStringEditor>
{
public:
    DECLARE_VALUE_EDITOR(IDD_REGSZ_EDITOR, IDS_REGSZ_TITLE)
    DECLARE_NO_SETTINGS()

    HRESULT InitDialog(__in INamedPropertyStore *pnps);
    HRESULT GetCurrentValue(__out PROPVARIANT *ppropvar);
};

HRESULT CStringEditor::InitDialog(__in INamedPropertyStore *pnps)
{
    CComPropVariant spropvar;
    HRESULT hr = _GetProperty(PKEY_RegistryData, &spropvar);
    if (SUCCEEDED(hr))
    {
        hr = SetDlgItemText(IDC_VALUEDATA, spropvar.pwszVal) ? S_OK : AtlHresultFromLastError();
    }
    return 0;
}

HRESULT CStringEditor::GetCurrentValue(__out PROPVARIANT *ppropvar)
{
    size_t cch = SendDlgItemMessage(IDC_VALUEDATA, WM_GETTEXTLENGTH, 0, 0);
    CLocalMemPtr<WCHAR> spsz;
    HRESULT hr = spsz.Allocate(cch + 1);
    if (SUCCEEDED(hr))
    {
        GetDlgItemText(IDC_VALUEDATA, spsz, (int)cch + 1);
        hr = InitPropVariantFromString(spsz, ppropvar);
    }
    return hr;
}

//------------------------------------------------------------------------------
// CMultiStringEditor
//------------------------------------------------------------------------------

class CMultiStringEditor :
    public CValueEditorBase<CMultiStringEditor>
{
public:
    DECLARE_VALUE_EDITOR(IDD_REGMULTISZ_EDITOR, IDS_REGMULTISZ_TITLE)
    DECLARE_NO_SETTINGS()

    HRESULT InitDialog(__in INamedPropertyStore *pnps);
    HRESULT GetCurrentValue(__out PROPVARIANT *ppropvar);

private:
    HRESULT _ConvertMultiSZToEditString(const BLOB &blob, __deref_out PWSTR *ppszString);
    HRESULT _ConvertEditStringToMultiSZ(PCWSTR pszString, size_t cch, __out PROPVARIANT *ppropvar, __out BOOL *pfEmpties);
};

HRESULT CMultiStringEditor::_ConvertMultiSZToEditString(const BLOB &blob, __deref_out PWSTR *ppszString)
{
    // First, determine how many embedded NULLs there are
    size_t cchIn = blob.cbSize / sizeof(WCHAR);
    size_t cNulls = 0;
    for (PCWSTR pszRead = (PCWSTR)blob.pBlobData + cchIn - 2; pszRead >= (PCWSTR)blob.pBlobData; pszRead--)
    {
        if (!*pszRead)
        {
            cNulls++;
        }
    }
    // Now walk the blob and convert embedded NULLs to CRLF
    CLocalMemPtr<WCHAR> spszString;
    HRESULT hr = spszString.Allocate(cchIn + cNulls);
    if (SUCCEEDED(hr))
    {
        PWSTR pszWrite = spszString;
        for (PCWSTR pszRead = (PCWSTR)blob.pBlobData; pszRead < (PCWSTR)blob.pBlobData + cchIn - 1; pszRead++)
        {
            if (!*pszRead)
            {
                *pszWrite++ = L'\r';
                *pszWrite++ = L'\n';
            }
            else
            {
                *pszWrite++ = *pszRead;
            }
        }
        *pszWrite = 0;
        *ppszString = spszString.Detach();
    }
    return hr;
}

HRESULT CMultiStringEditor::InitDialog(__in INamedPropertyStore *pnps)
{
    CComPropVariant spropvar;
    HRESULT hr = _GetProperty(PKEY_RegistryData, &spropvar);
    if (SUCCEEDED(hr))
    {
        CLocalMemPtr<WCHAR> spszMultiString;
        hr = _ConvertMultiSZToEditString(spropvar.blob, &spszMultiString);
        if (SUCCEEDED(hr))
        {
            hr = SetDlgItemText(IDC_VALUEDATA, spszMultiString) ? S_OK : AtlHresultFromLastError();
        }
    }
    return hr;
}

HRESULT CMultiStringEditor::_ConvertEditStringToMultiSZ(PCWSTR pszString, size_t cch, __out PROPVARIANT *ppropvar, __out BOOL *pfEmpties)
{
    // First, allocate a buffer large enough to do the conversion
    CLocalMemPtr<WCHAR> spszBuffer;
    HRESULT hr = spszBuffer.Allocate(cch + 2);  // 2 trailing NULLs
    if (SUCCEEDED(hr))
    {
        // Walk the string and copy text, replacing CRLF with embedded NULLs and skipping empty substrings
        BOOL fEmpties = FALSE;
        size_t cb = 0;
        PCWSTR pszRead = pszString;
        PWSTR pszWrite = spszBuffer;
        while (*pszRead)
        {
            if (*pszRead == L'\r' && *(pszRead + 1) == L'\n')
            {
                fEmpties = TRUE;
                pszRead += 2;
            }
            else
            {
                *pszWrite++ = *pszRead++;
                cb += sizeof(WCHAR);
                if (*pszRead == L'\r' && *(pszRead + 1) == L'\n')
                {
                    *pszWrite++ = 0;
                    cb += sizeof(WCHAR);
                    pszRead += 2;
                }
                else if (!*pszRead)
                {
                    *pszWrite++ = 0;
                    cb += sizeof(WCHAR);
                }
            }
        }
        *pszWrite = 0;
        cb += sizeof(WCHAR);

        // Don't consider a completely empty value to have empty substrings
        if (fEmpties && cb == sizeof(WCHAR))
        {
            fEmpties = FALSE;
        }

        // Allocate the blob
        CComMemPtr<BYTE> spblob;
        hr = spblob.Allocate(cb);
        if (SUCCEEDED(hr))
        {
            CopyMemory(spblob, spszBuffer, cb);
            ppropvar->vt = VT_BLOB;
            ppropvar->blob.pBlobData = spblob.Detach();
            ppropvar->blob.cbSize = (ULONG)cb;
            *pfEmpties = fEmpties;
        }
    }
    return hr;
}

HRESULT CMultiStringEditor::GetCurrentValue(__out PROPVARIANT *ppropvar)
{
    size_t cch = SendDlgItemMessage(IDC_VALUEDATA, WM_GETTEXTLENGTH, 0, 0);
    CLocalMemPtr<WCHAR> spsz;
    HRESULT hr = spsz.Allocate(cch + 1);
    if (SUCCEEDED(hr))
    {
        GetDlgItemText(IDC_VALUEDATA, spsz, (int)cch + 1);

        BOOL fEmpties;
        hr = _ConvertEditStringToMultiSZ(spsz, cch, ppropvar, &fEmpties);
        if (SUCCEEDED(hr) && fEmpties)
        {
            int itdOut;
            TaskDialog(m_hWnd, _AtlBaseModule.GetModuleInstance(), MAKEINTRESOURCE(IDS_TD_TITLE), MAKEINTRESOURCE(IDS_TD_EMPTIESREMOVEDINSTRUCTION),
                       MAKEINTRESOURCE(IDS_TD_EMPTIESREMOVEDCONTENT), TDCBF_OK_BUTTON, TD_INFORMATION_ICON, &itdOut); 
        }
    }
    return hr;
}

//------------------------------------------------------------------------------
// CNumericEdit
//------------------------------------------------------------------------------

enum NUMERICEDITBASE
{
    NEB_DECIMAL = 0xa,
    NEB_HEX = 0x10,
};

template <typename TType>
class CNumericEdit : 
    public CWindowImpl<CNumericEdit<TType>>
{
public:
    DECLARE_WND_SUPERCLASS("NumericEdit", WC_EDIT);

    CNumericEdit() : _neb(NEB_DECIMAL), _cDigits(0)  {};

    BEGIN_MSG_MAP(CNumericEdit)
        MESSAGE_HANDLER(WM_CREATE, _OnCreate)
        MESSAGE_HANDLER(WM_CHAR, _OnChar)
        MESSAGE_HANDLER(WM_PASTE, _OnPaste)
    END_MSG_MAP()

    HRESULT SubclassWindow(HWND hwnd);
    HRESULT SetBase(NUMERICEDITBASE neb);

    HRESULT GetValue(TType &type);
    HRESULT SetValue(TType type);

private:
    HRESULT _ShowBalloonTip(UINT uiTitleID, UINT uiTextID, INT tti);
    void _CalcDigits(BOOL fForceUpdate = FALSE);
    BOOL _IsValidDigit(WCHAR wc);
    HRESULT _StringFromValue(TType type, NUMERICEDITBASE neb, __out_ecount(cch) PWSTR psz, DWORD cch);
    HRESULT _ValueFromString(PCWSTR psz, TType &type);

    // Message handlers
    LRESULT _OnCreate(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnChar(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnPaste(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);

private:
    NUMERICEDITBASE _neb;
    UINT _cDigits;
};

template <typename TType>
void CNumericEdit<TType>::_CalcDigits(BOOL fForceUpdate)
{
    double cDigits = floor(log(pow((double)2,(int)(sizeof(TType) * 8)))/log((double)_neb)) + (_neb == NEB_DECIMAL ? 1 : 0);
    if (cDigits != _cDigits || fForceUpdate)
    {
        _cDigits = (UINT)cDigits;
        if (IsWindow())
        {
            DefWindowProcW(EM_LIMITTEXT, _cDigits, NULL);
        }
    }
}

template <typename TType>
HRESULT CNumericEdit<TType>::SubclassWindow(HWND hwnd)
{
    CBufString strClass;
    HRESULT hr = GetClassName(hwnd, strClass, strClass.Size()) ? S_OK : AtlHresultFromLastError();
    if (SUCCEEDED(hr))
    {
        hr = E_INVALIDARG;
        if (lstrcmpi(WC_EDIT, strClass) == 0)
        {
            hr = CWindowImpl<CNumericEdit<TType>>::SubclassWindow(hwnd) ? S_OK : E_FAIL;
            if (SUCCEEDED(hr))
            {
                _CalcDigits(TRUE);
            }
        }
    }
    return hr;
}

template <typename TType>
HRESULT CNumericEdit<TType>::SetBase(NUMERICEDITBASE neb)
{
    ATLASSERT(neb == NEB_DECIMAL || neb == NEB_HEX);

    HRESULT hr = S_OK;
    if (neb != _neb)
    {
        TType value;
        hr = GetValue(value);
        if (SUCCEEDED(hr))
        {
            _neb = neb;
            _CalcDigits();
            hr = SetValue(value);
        }
    }
    return hr;
}

template <typename TType>
HRESULT CNumericEdit<TType>::_ValueFromString(PCWSTR psz, TType &type)
{
    type = 0;
    HRESULT hr = S_OK;
    try
    {
        SafeInt<TType> siValue(0);
        for (PCWSTR pszRead = psz; *pszRead && SUCCEEDED(hr); pszRead++)
        {
            BYTE b;
            if (_neb == NEB_DECIMAL || *pszRead >= L'0' && *pszRead <= L'9')
            {
                b = *pszRead - L'0';
            }
            else if (*pszRead >= L'a' && *pszRead <= L'f')
            {
                b = *pszRead - L'a' + 10;
            }
            else if (*pszRead >= L'A' && *pszRead <= L'F')
            {
                b = *pszRead - L'A' + 10;
            }
            else
            {
                hr = E_INVALIDARG;
            }

            if (SUCCEEDED(hr))
            {
                if (_neb == NEB_HEX)
                {
                    siValue = (siValue << 4) + b;
                }
                else
                {
                    siValue = (siValue * 10) + b;
                }
            }
        }
        type = siValue;
    }
    catch (SafeIntException e)
    {
        switch (e.m_code)
        {
        case SafeIntArithmeticOverflow:
            hr = AtlHresultFromWin32(ERROR_ARITHMETIC_OVERFLOW);
            break;

        default:
            hr = E_FAIL;
            break;
        }
    }
    return hr;
}

template <typename TType>
HRESULT CNumericEdit<TType>::GetValue(TType &type)
{
    HRESULT hr = S_OK;
    type = 0;
    CBufString str;
    if (GetWindowText(str, str.Size()))
    {
        hr = _ValueFromString(str, type);
    }
    return hr;
}

template <typename TType>
HRESULT CNumericEdit<TType>::_StringFromValue(TType type, NUMERICEDITBASE neb, __out_ecount(cch) PWSTR psz, DWORD cch)
{
    HRESULT hr = S_OK;
    PWSTR pszWrite = psz;
    do
    {
        hr = cch-- > 1 ? S_OK : STRSAFE_E_INSUFFICIENT_BUFFER;
        if (SUCCEEDED(hr))
        {
            UINT uiDigit = (UINT)(type % neb);
            type /= neb;
            if (uiDigit > 9)
            {
                *pszWrite++ = uiDigit - 10 + L'a';
            }
            else
            {
                *pszWrite++ = uiDigit + L'0';
            }
        }
    } while (type > 0 && SUCCEEDED(hr));

    *pszWrite = 0;

    // Reverse the order, even in the event of insufficient buffer
    PWSTR pszLast = pszWrite - 1;
    PWSTR pszFirst = psz;
    while (pszFirst < pszLast)
    {
        WCHAR wc = *pszLast;
        *pszLast-- = *pszFirst;
        *pszFirst++ = wc;
    }
    return hr;
}

template <typename TType>
HRESULT CNumericEdit<TType>::SetValue(TType type)
{
    CBufString str;
    HRESULT hr = _StringFromValue(type, _neb, str, str.Size());
    if (SUCCEEDED(hr))
    {
        hr = SetWindowText(str) ? S_OK : AtlHresultFromLastError();
    }
    return hr;
}

template <typename TType>
LRESULT CNumericEdit<TType>::_OnCreate(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    _CalcDigits();
    fHandled = FALSE;
    return 0;
}

template <typename TType>
BOOL CNumericEdit<TType>::_IsValidDigit(WCHAR wc)
{
    BOOL fValid = FALSE;
    wc = (WCHAR)CharLower((PWSTR)wc);
    if ((wc >= L'0' && wc <= L'9') || wc == VK_BACK)
    {
        fValid = TRUE;
    }
    else if (_neb == NEB_HEX && wc >= L'a' && wc <= L'f')
    {
        fValid = TRUE;
    }
    return fValid;
}

template <typename TType>
HRESULT CNumericEdit<TType>::_ShowBalloonTip(UINT uiTitleID, UINT uiTextID, INT tti)
{
    CBufString strTitle;
    HRESULT hr = strTitle.Load(uiTitleID);
    if (SUCCEEDED(hr))
    {
        CBufString strText;
        hr = strText.Load(uiTextID);
        if (SUCCEEDED(hr))
        {
            EDITBALLOONTIP ebt;
            ebt.cbStruct = sizeof(ebt);
            ebt.pszTitle = strTitle;
            ebt.pszText = strText;
            ebt.ttiIcon = tti;
            hr = DefWindowProcW(EM_SHOWBALLOONTIP, 0, (LPARAM)&ebt) ? S_OK : E_FAIL;
        }
    }
    return hr;
}

template <typename TType>
LRESULT CNumericEdit<TType>::_OnChar(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    if (GetKeyState(VK_CONTROL) < 0 || GetWindowTextLength() == _cDigits || _IsValidDigit((WCHAR)wParam))
    {
        fHandled = FALSE;
    }
    else
    {
        if (FAILED(_ShowBalloonTip(IDS_NUMERICEDIT_INVALIDCHARTITLE, _neb == NEB_DECIMAL ? IDS_NUMERICEDIT_DECIMALMESSAGE : IDS_NUMERICEDIT_HEXMESSAGE, TTI_ERROR)))
        {
            MessageBeep(MB_OK);
        }
    }
    return 0;
}

template <typename TType>
LRESULT CNumericEdit<TType>::_OnPaste(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    TType type;
    HRESULT hr = GetValue(type);
    if (SUCCEEDED(hr))
    {
        SetRedraw(FALSE);
        DefWindowProcW(WM_PASTE, wParam, lParam);
        CBufString str;
        GetWindowText(str, str.Size());
        TType typenew;
        hr = _ValueFromString(str, typenew);
        if (FAILED(hr))
        {
            SetValue(type);

            UINT uiTextID;
            switch (hr)
            {
            case E_INVALIDARG:
                uiTextID = IDS_NUMERICEDIT_PASTEARG;
                break;

            case __HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW):
                uiTextID = IDS_NUMERICEDIT_PASTEOVERFLOW;
                break;

            default:
                uiTextID = IDS_NUMERICEDIT_PASTEFAILED;
                break;
            }

            _ShowBalloonTip(IDS_NUMERICEDIT_INVALIDPASTETITLE, uiTextID, TTI_ERROR);
        }
        SetRedraw(TRUE);
        InvalidateRect(NULL, FALSE);
    }
    fHandled = TRUE;
    return 0;
}

//------------------------------------------------------------------------------
// CNumberEditor
//------------------------------------------------------------------------------

DECLARE_SZ(BaseValue, "NumberEditorBase")

template<typename TType, UINT uiTitle>
class CNumberEditor :
    public CValueEditorBase<CNumberEditor<TType, uiTitle>>
{
public:
    DECLARE_VALUE_EDITOR(IDD_REGNUMBER_EDITOR, uiTitle)

    BEGIN_MSG_MAP(CDWordEditor)
        COMMAND_RANGE_CODE_HANDLER(IDC_BASE_HEX, IDC_BASE_DECIMAL, BN_CLICKED, _OnBaseClick)
        CHAIN_MSG_MAP(CValueEditorBase)
    END_MSG_MAP()

    HRESULT InitDialog(__in INamedPropertyStore *pnps);
    HRESULT GetCurrentValue(__out PROPVARIANT *ppropvar);
    HRESULT SaveSettings(__in INamedPropertyStore *pnps);

private:
    HRESULT _GetCheckedRadioButton(UINT uiFirst, UINT uiLast, UINT *puiChecked);
    HRESULT _InitializeSettings(__in INamedPropertyStore *pnps);
    HRESULT _OnBaseChanged();

    // Command handlers
    LRESULT _OnBaseClick(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL &fHandled);

private:
    CNumericEdit<TType> _edt;
};

template<typename TType, UINT uiTitle>
HRESULT CNumberEditor<TType, uiTitle>::_GetCheckedRadioButton(UINT uiFirst, UINT uiLast, UINT *puiChecked)
{
    HRESULT hr = E_FAIL;
    *puiChecked = -1;
    for (UINT i = uiFirst; i <= uiLast && FAILED(hr); i++)
    {
        if (IsDlgButtonChecked(i) == BST_CHECKED)
        {
            *puiChecked = i;
            hr = S_OK;
        }
    }
    return hr;
}

template<typename TType, UINT uiTitle>
HRESULT CNumberEditor<TType, uiTitle>::_OnBaseChanged()
{
    UINT uiID;
    HRESULT hr = _GetCheckedRadioButton(IDC_BASE_HEX, IDC_BASE_DECIMAL, &uiID);
    if (SUCCEEDED(hr))
    {
        hr = _edt.SetBase(uiID == IDC_BASE_HEX ? NEB_HEX : NEB_DECIMAL);
    }
    return hr;
}

template<typename TType, UINT uiTitle>
HRESULT CNumberEditor<TType, uiTitle>::_InitializeSettings(__in INamedPropertyStore *pnps)
{
    CComPropVariant sppvBase;
    HRESULT hr = pnps->GetNamedValue(c_szBaseValue, &sppvBase);
    if (SUCCEEDED(hr))
    {
        NUMERICEDITBASE neb = NEB_HEX;
        if (sppvBase.vt == VT_UI4 && (sppvBase.ulVal == NEB_HEX || sppvBase.ulVal == NEB_DECIMAL))
        {
            neb = (NUMERICEDITBASE) sppvBase.ulVal;
        }
        hr = CheckRadioButton(IDC_BASE_HEX, IDC_BASE_DECIMAL, neb == NEB_HEX ? IDC_BASE_HEX : IDC_BASE_DECIMAL) ? S_OK : AtlHresultFromLastError();
        if (SUCCEEDED(hr))
        {
            hr = _OnBaseChanged();
        }
    }
    return hr;
}

template<typename TType, UINT uiTitle>
HRESULT CNumberEditor<TType, uiTitle>::InitDialog(__in INamedPropertyStore *pnps)
{
    HRESULT hr = _edt.SubclassWindow(GetDlgItem(IDC_VALUEDATA));
    if (SUCCEEDED(hr))
    {
        hr = _InitializeSettings(pnps);
        if (SUCCEEDED(hr))
        {
            hr = _OnBaseChanged();
            if (SUCCEEDED(hr))
            {
                CComPropVariant spropvar;
                hr = _GetProperty(PKEY_RegistryData, &spropvar);
                if (SUCCEEDED(hr))
                {
                    hr = _edt.SetValue((TType)spropvar);
                }
            }
        }
    }
    return hr;
}

template<typename TType, UINT uiTitle>
HRESULT CNumberEditor<TType, uiTitle>::GetCurrentValue(__out PROPVARIANT *ppropvar)
{
    TType t;
    HRESULT hr = _edt.GetValue(t);
    if (SUCCEEDED(hr))
    {
        CComPropVariant spropvar(t);
        hr = spropvar.Detach(ppropvar);
    }
    return hr;
}

template<typename TType, UINT uiTitle>
HRESULT CNumberEditor<TType, uiTitle>::SaveSettings(__in INamedPropertyStore *pnps)
{
    UINT uiID;
    HRESULT hr = _GetCheckedRadioButton(IDC_BASE_HEX, IDC_BASE_DECIMAL, &uiID);
    if (SUCCEEDED(hr))
    {
        CComPropVariant sppvBase;
        hr = InitPropVariantFromUInt32(uiID == IDC_BASE_HEX ? NEB_HEX : NEB_DECIMAL, &sppvBase);
        if (SUCCEEDED(hr))
        {
            hr = pnps->SetNamedValue(c_szBaseValue, sppvBase);
        }
    }
    return hr;
}

template<typename TType, UINT uiTitle>
LRESULT CNumberEditor<TType, uiTitle>::_OnBaseClick(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL &fHandled)
{
    _OnBaseChanged();
    return 0;
}

//------------------------------------------------------------------------------
// CBinaryEditor
//------------------------------------------------------------------------------

DECLARE_SZ(HexCellTypeValue, "HexCellType");
DECLARE_SZ(HexCellDividerTypeValue, "HexCellDividerType");
DECLARE_SZ(AlternateRowColorValue, "AlternateRowColor");
DECLARE_SZ(HexEditorSizeValue, "HexEditorSize");

class CBinaryEditor :
    public CValueEditorBase<CBinaryEditor>
{
public:
    BEGIN_MSG_MAP(CBinaryEditor)
        MESSAGE_HANDLER(WM_SIZE, _OnSize)
        CHAIN_MSG_MAP(CValueEditorBase)
    END_MSG_MAP()

    DECLARE_VALUE_EDITOR(IDD_REGBINARY_EDITOR, IDS_REGBINARY_TITLE)

    HRESULT InitDialog(__in INamedPropertyStore *pnps);
    HRESULT GetCurrentValue(__out PROPVARIANT *ppropvar);
    HRESULT SaveSettings(__in INamedPropertyStore *pnps);

private:
    HRESULT _InitializeSettings(__in INamedPropertyStore *pnps);

    // Message handlers
    LRESULT _OnSize(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);

private:
    CSize _size;
};

static const struct
{
    PCWSTR pszValueName;
    UINT uiMsgGet;
    UINT uiMsgSet;
}
c_rgDWordSettings[] =
{
    { c_szHexCellTypeValue, BEM_GETHEXCELLTYPE, BEM_SETHEXCELLTYPE },
    { c_szHexCellDividerTypeValue, BEM_GETHEXCELLDIVIDERTYPE, BEM_SETHEXCELLDIVIDERTYPE },
    { c_szAlternateRowColorValue, BEM_GETALTERNATEROWCOLOR, BEM_SETALTERNATEROWCOLOR },
};

HRESULT CBinaryEditor::_InitializeSettings(__in INamedPropertyStore *pnps)
{
    HRESULT hr = S_OK;
    for (UINT i = 0; i < ARRAYSIZE(c_rgDWordSettings) && SUCCEEDED(hr); i++)
    {
        CComPropVariant spropvar;
        hr = pnps->GetNamedValue(c_rgDWordSettings[i].pszValueName, &spropvar);
        if (SUCCEEDED(hr) && spropvar.vt != VT_EMPTY)
        {
            SendDlgItemMessage(IDC_VALUEDATA, c_rgDWordSettings[i].uiMsgSet, 0, spropvar.ulVal);
        }
    }
    if (SUCCEEDED(hr))
    {
        CComPropVariant spropvar;
        hr = pnps->GetNamedValue(c_szHexEditorSizeValue, &spropvar);
        if (SUCCEEDED(hr) && spropvar.vt != VT_EMPTY)
        {
            CSize size(spropvar.ulVal);
            SetWindowPos(NULL, 0, 0, size.cx, size.cy, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
        }
    }
    return hr;
}

HRESULT CBinaryEditor::InitDialog(__in INamedPropertyStore *pnps)
{
    HRESULT hr = E_FAIL;
    HWND hwndPlaceholder = GetDlgItem(IDC_PLACEHOLDER);
    if (hwndPlaceholder)
    {
        HWND hwndBinaryEdit;
        RECT rc;
        ::GetWindowRect(hwndPlaceholder, &rc);
        HWND hwndPrev = ::GetWindow(hwndPlaceholder, GW_HWNDPREV);
        ScreenToClient(&rc);
        hr = CreateBinaryEdit(m_hWnd, BES_TOOLBAR | WS_BORDER | WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL, rc, IDC_VALUEDATA, &hwndBinaryEdit);
        if (SUCCEEDED(hr))
        {
            ::SetWindowPos(hwndBinaryEdit, hwndPrev, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
            ::DestroyWindow(hwndPlaceholder);

            CComPropVariant spropvar;
            hr = _GetProperty(PKEY_RegistryData, &spropvar);
            if (SUCCEEDED(hr))
            {
                SendDlgItemMessage(IDC_VALUEDATA, BEM_SETDATA, spropvar.blob.cbSize, (LPARAM)spropvar.blob.pBlobData);
                CRect rc;
                GetClientRect(&rc);
                _size.cx = rc.Width();
                _size.cy = rc.Height();

                hr = _InitializeSettings(pnps);
            }
        }
    }
    return hr;
}

HRESULT CBinaryEditor::GetCurrentValue(__out PROPVARIANT *ppropvar)
{
    HRESULT hr = S_OK;
    ULONG cb = (ULONG)SendDlgItemMessage(IDC_VALUEDATA, BEM_GETDATASIZE, 0, 0);
    if (cb)
    {
        CComMemPtr<BYTE> spblob;
        hr = spblob.Allocate(cb);
        if (SUCCEEDED(hr))
        {
            SendDlgItemMessage(IDC_VALUEDATA, BEM_GETDATA, cb, (LPARAM)((BYTE*)spblob));
            ppropvar->blob.cbSize = cb;
            ppropvar->blob.pBlobData = spblob.Detach();
            ppropvar->vt = VT_BLOB;
        }
    }
    return hr;
}

HRESULT CBinaryEditor::SaveSettings(__in INamedPropertyStore *pnps)
{
    HRESULT hr = S_OK;
    for (UINT i = 0; i < ARRAYSIZE(c_rgDWordSettings) && SUCCEEDED(hr); i++)
    {
        DWORD dwVal = (DWORD)SendDlgItemMessage(IDC_VALUEDATA, c_rgDWordSettings[i].uiMsgGet, 0, 0);
        CComPropVariant spropvar;
        hr = InitPropVariantFromUInt32(dwVal, &spropvar);
        if (SUCCEEDED(hr))
        {
            hr = pnps->SetNamedValue(c_rgDWordSettings[i].pszValueName, spropvar);
        }
    }
    if (SUCCEEDED(hr))
    {
        CRect rc;
        GetWindowRect(&rc);
        CSize size(rc.Width(), rc.Height());
        CComPropVariant spropvar;
        hr = InitPropVariantFromUInt32(MAKELONG(size.cx, size.cy), &spropvar);
        if (SUCCEEDED(hr))
        {
            hr = pnps->SetNamedValue(c_szHexEditorSizeValue, spropvar);
        }
    }
    return hr;
}

LRESULT CBinaryEditor::_OnSize(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    enum LAYOUTTYPE
    {
        LT_SIZE,
        LT_MOVE
    };

    static const struct
    {
        UINT uidControl;
        LAYOUTTYPE lt;
    }
    c_rgLayout[] =
    {
        { IDC_VALUEDATA, LT_SIZE },
        { IDOK, LT_MOVE },
        { IDCANCEL, LT_MOVE },
    };
    CSize sizeNew(LOWORD(lParam), HIWORD(lParam));
    int dx = sizeNew.cx - _size.cx;
    int dy = sizeNew.cy - _size.cy;
    HDWP hdwp = BeginDeferWindowPos(ARRAYSIZE(c_rgLayout));
    for (int i = 0; i < ARRAYSIZE(c_rgLayout); i++)
    {
        HWND hwndCtl = GetDlgItem(c_rgLayout[i].uidControl);
        if (hwndCtl)
        {
            CRect rcCtl;
            ::GetWindowRect(hwndCtl, &rcCtl);
            switch (c_rgLayout[i].lt)
            {
            case LT_SIZE:
                ::DeferWindowPos(hdwp, hwndCtl, NULL, 0, 0, rcCtl.Width() + dx, rcCtl.Height() + dy, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
                break;

            case LT_MOVE:
                ScreenToClient(&rcCtl);
                ::DeferWindowPos(hdwp, hwndCtl, NULL, rcCtl.left + dx, rcCtl.top + dy, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
                break;

            }
        }
    }
    EndDeferWindowPos(hdwp);
    _size = sizeNew;
    return 0;
}

//------------------------------------------------------------------------------

STDAPI CreateValueEditor(__in IShellItem2 *psi, REFIID riid, __deref_out void **ppv)
{
    *ppv = NULL;

    PROPERTYKEY keys[3] = { PKEY_ItemNameDisplay, PKEY_RegistryType, PKEY_RegistryData };
    CComPtr<IPropertyStore> sppropstore;
    HRESULT hr = psi->GetPropertyStoreForKeys(keys, ARRAYSIZE(keys), GPS_DEFAULT, IID_PPV_ARGS(&sppropstore));
    if (SUCCEEDED(hr))
    {
        CComPropVariant spropvarType;
        hr = sppropstore->GetValue(PKEY_RegistryType, &spropvarType);
        if (SUCCEEDED(hr))
        {
            ATLASSERT(spropvarType.vt == VT_UI4);

            static const struct
            {
                UINT uiRegType;
                HRESULT (*pfnCreateInstance)(__in IShellItem2 *, __in IPropertyStore *, REFIID, __deref_out void **);
            }
            c_rgEditorMap[] = 
            {
                { REG_SZ, CValueEditorBase<CStringEditor>::s_CreateEditor },
                { REG_MULTI_SZ, CValueEditorBase<CMultiStringEditor>::s_CreateEditor },
                { REG_EXPAND_SZ, CValueEditorBase<CStringEditor>::s_CreateEditor },
                { REG_DWORD, CValueEditorBase<CNumberEditor<DWORD, IDS_REGDWORD_TITLE>>::s_CreateEditor },
                { REG_QWORD, CValueEditorBase<CNumberEditor<ULONGLONG, IDS_REGQWORD_TITLE>>::s_CreateEditor },
                { REG_BINARY, CValueEditorBase<CBinaryEditor>::s_CreateEditor },
            };

            hr = E_UNEXPECTED;
            for (int i = 0; i < ARRAYSIZE(c_rgEditorMap) && !*ppv; i++)
            {
                if (spropvarType.uintVal == c_rgEditorMap[i].uiRegType)
                {
                    hr = c_rgEditorMap[i].pfnCreateInstance(psi, sppropstore, riid, ppv);
                }
            }
        }
    }
    return hr;
}
